import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class HumbleDir {
    static final HashSet<String> skipDownloadsClasses;
    static {
        skipDownloadsClasses=new HashSet<String>();
        skipDownloadsClasses.add("linux");
        skipDownloadsClasses.add("mac");
        skipDownloadsClasses.add("android");
    }

    private final Path downloadDir;
    private final LinkedList<HumbleFile> files;
    private final HumbleXML tracking;

    private boolean performDownloads(Element group) {
        Set<String> classes=group.classNames();
        for (String skipClass : skipDownloadsClasses) {
            if (classes.contains(skipClass)) {
                return false;
            }
        }
        return true;
    }

    public HumbleDir(Element row) throws Exception {
        String name=getRowName(row);
        System.out.format("Processing \"%s\"",name).println();
        downloadDir=DownloadDir.get().resolve(name);
        tracking=new HumbleXML(downloadDir);
        //noinspection ResultOfMethodCallIgnored
        downloadDir.toFile().mkdir();
        files=new LinkedList<HumbleFile>();
        Elements groups=row.select("div.downloads");
        for (Element group : groups) {
            if (performDownloads(group)) {
                Elements downloads=group.select("div.download");
                for (Element download : downloads) {
                    HumbleFile file=new HumbleFile(download);
                    if (file.isValid()) {
                        files.add(file);
                    }
                }
            }
        }
        tracking.commit();
    }

    public void download() {
        for (HumbleFile file : files) {
            file.download();
        }
    }

    private class HumbleFile {
        private final URL downloadURL;
        private final Path downloadFile;
        private final MD5 md5;

        private boolean performDelete(Path filePath) throws IOException {
            if (filePath.toFile().exists()) {
                Files.delete(filePath);
                return true;
            }
            return false;
        }

        public HumbleFile(Element download) throws IOException {
            downloadURL=getDownloadURL(download);
            if (downloadURL!=null) {
                String filename=Paths.get(downloadURL.getPath()).getFileName().toString();
                downloadFile=downloadDir.resolve(filename);
                md5=getDownloadMD5(download);
                MD5 trackedMD5=tracking.getMD5(filename);
                if (trackedMD5!=null && !md5.equals(trackedMD5)) {
                    boolean deleted=false;
                    if (performDelete(downloadFile)) deleted=true;
                    if (performDelete(Download.getPartialFilePath(downloadFile))) deleted=true;
                    if (deleted) {
                        System.out.format("  deleted \"%s\" due to MD5 change",filename).println();
                    }
                 }
                tracking.setMD5(filename,md5);
            }
            else {
                downloadFile=null;
                md5=null;
            }
        }

        public boolean isValid() {
            return downloadURL!=null;
        }

        private boolean needsDownload() {
            boolean needs=true;
            if (Files.exists(downloadFile)) {
                try {
                    MD5 fileMD5=new MD5(downloadFile);
                    if (fileMD5.equals(md5)) {
                        needs=false;
                    }
                }
                catch(Exception e) {
                    System.err.print("Unable to determine md5: ");
                    System.err.println(e.getMessage());
                    needs=false; // what would be a better option? Ask user?
                }
            }
            return needs;
         }

        public void download() {
            if (needsDownload()) {
                Download.perform(downloadURL,downloadFile);
            }
        }
    }

    static private String getRowName(Element row) {
        Set<String> names=row.classNames();
        names.remove("row");
        assert names.size()==1;
        return names.toArray(new String[1])[0];
    }

    static private URL getDownloadURL(Element download) {
        Elements urls=download.select("a.a[data-web]");
        if (urls.size()==1) {
            String url=urls.first().attr("data-web");
            try {
                return new URL(url);
            }
            catch(Exception e) {
                System.err.print("Invalid download URL encountered: ");
                System.err.println(url);
            }
        }
        return null;
    }

    static private MD5 getDownloadMD5(Element download) {
        Elements md5s=download.select("div.dldetails a.dlmd5[href~=#[0-9a-f]*]");
        assert md5s.size()==1;
        String md5=md5s.first().attr("href");
        return new MD5(md5.substring(1));
    }
}
